#include <fstream>
#include <vector>
#include <string>
#include "token.hpp"
#include "utility_functions.hpp"
#include "list_of_commands.hpp"
#include "list_of_operators.hpp"

std::vector<std::string> load_file_into_std_strings(char *file_name);
std::vector<token> tokenise(std::vector<std::string> lines);

std::vector<token> load_program(char *file_name)
{
    std::vector<std::string> whole_file = load_file_into_std_strings(file_name);
    return tokenise(whole_file);
}


std::vector<std::string> load_file_into_std_strings(char *file_name)
{
    std::ifstream myfile(file_name);
    std::vector<std::string> whole_file;
    std::string s;
    if(myfile.is_open())
    {
        while(!myfile.eof())
        {
            getline(myfile, s);
            whole_file.push_back(s);
        }
    }
    else
    {
        error_and_terminate("Unable to open file");
    }
    return whole_file;
}

std::vector<token> tokenise(std::vector<std::string> lines)
{
    std::vector<token> program;
    std::vector<std::string> commands = get_list_of_commands();
    std::vector<std::string> operators = get_list_of_operators();
    bool parsing_first_token_on_line = true; // for line numbers
    std::string line;
    while(!lines.empty())
    {
        line = lines[0];
        lines.erase(lines.begin());


        next_token:

        line = TRIM(line);

        // if line is empty still put an end of line token on the token list for debugging, to know what line the error occured on
        if(line.empty()) // already checked if lines is not empty
        {
            parsing_first_token_on_line = true; //reset
            program.push_back(token(token::end_of_line, "\\n"));
            continue; // this could be the last line
        }

         // already checked if the first line is empty, here we are doing line numbers
        if(isdigit(line[0]) and parsing_first_token_on_line)
        {
            parsing_first_token_on_line = false;
            std::string number = "";
            while(!line.empty() and isdigit(line[0]))
            {
                number.push_back(line[0]);
                line.erase(0, 1);
            }
            program.push_back(token(token::line_number, number));
            goto next_token;
        }

        parsing_first_token_on_line = false;

        // handle quotes/string literals
        if(line[0] == '\"')
        {
            std::string literal = "\"";
            line.erase(0, 1);
            while(!line.empty() and line[0] != '\"')
            {
                literal.push_back(line[0]);
                line.erase(0, 1);
            }

            if(line.empty()) // no closing qoute error checking
            {
                error_and_terminate("no closing qoute");
            }

            // if all went well
            literal.push_back('\"');
            line.erase(0, 1);

            program.push_back(token(token::literal, literal));
            goto next_token;
        }

        // handle constants note 1 dot is allowed
        if(isdigit(line[0]))
        {
            std::string number_as_string = "";
            number_as_string.push_back(line[0]);
            line.erase(0, 1);
            while(!line.empty() and isdigit(line[0]))
            {
                number_as_string.push_back(line[0]);
                line.erase(0, 1);
            }

            if(!line.empty() and line[0] == '.')
            {
                number_as_string.push_back('.');
                line.erase(0, 1);

                while(!line.empty() and isdigit(line[0]))
                {
                    number_as_string.push_back(line[0]);
                    line.erase(0, 1);
                }
            }

            program.push_back(token(token::constant, number_as_string));
            goto next_token;
        }

        // handle commas
        if(line[0] == ',')
        {
            line.erase(0, 1);
            program.push_back(token(token::comma, ","));
            goto next_token;
        }

        // handle semicolons
        if(line[0] == ';')
        {
            line.erase(0, 1);
            program.push_back(token(token::semicolon, ";"));
            goto next_token;
        }

        //handle parenthesis
        if(line[0] == '(' or line[0] == ')')
        {
            program.push_back(token(token::paren, line.substr(0, 1)));
            line.erase(0, 1);
            goto next_token;
        }

        // handle commands, identifiers, and operators. Some of the possible ways these can be mede up resemble each other.
        // commands only contain letters, identifiers can contain letters, underscores and at the end a dollar sign, and operators can contain
        // special symbols such as '+' and '<>', and some are letters such as MOD and XOR
        if(isalpha(line[0])) // is command, identifier or operator
        {
            std::string token_value = "";

            while(!line.empty() and isalpha(line[0]))
            {
                token_value.push_back(line[0]);
                line.erase(0, 1);
            }
            if((!line.empty() and (line[0] != '_' or line[0] != '$')) or line.empty()) // if this holds then token_value must be a command or an operator
            {
                if(contains<std::string>(UCASE(token_value), commands)) // must be a command
                {
                    program.push_back(token(token::command, UCASE(token_value)));
                    goto next_token;
                }
                if(contains<std::string>(UCASE(token_value), operators)) // must be an operator
                {
                    program.push_back(token(token::op, UCASE(token_value)));
                    goto next_token;
                }
                if(line.empty())
                {
                    program.push_back(token(token::identifier, token_value));
                    goto next_token;
                }
            }
            if((!line.empty() and (isalpha(line[0]) or line[0] == '_' or line[0] == '$'))) // checks if it is an identifier, here I am allowing $ signs in the middle of variable names. I might change this later.
            {
                while(!line.empty() and (isalpha(line[0]) or line[0] == '_' or line[0] == '$'))
                {
                    token_value.push_back(line[0]);
                    line.erase(0, 1);
                }
            }
            program.push_back(token(token::identifier, token_value));
            goto next_token;
        }

        // operator
        if(line[0] == '+' or line[0] == '-' or line[0] == '*' or line[0] == '/' or line[0] == '\\' or line[0] == '^' or line[0] == '<' or line[0] == '=' or line[0] == '>')
        {
            std::string op = "";
            if((line[0] == '<' or line[0] == '>') and ((line.size()>1) and (line[1] == '=' or line[1] == '>'))) // <>, <=, or >=. note this will let >> through but that is no big deal as an error will be caught later. I will come back to this
            {
                op.push_back(line[0]);
                op.push_back(line[1]);
                line.erase(0, 2);
                program.push_back(token(token::op, op));
                goto next_token;
            }
            op.push_back(line[0]);
            line.erase(0, 1);
            program.push_back(token(token::op, op));
            goto next_token;
        }

        // colon, for putting multiple statements on a line
        if(line[0] == ':')
        {
            line.erase(0, 1);
            program.push_back(token(token::end_of_line, ":"));
            goto next_token;
        }

        error_and_terminate("unrecognised token");

    }
    program.push_back(token(token::end_of_file, ""));

    return program;
}
